package com.iakuil.app.common.core.util;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;

/**
 * 带缓存的反射工具类
 *
 * <p>非必要不使用反射。
 *
 * @author Kai
 */
@UtilityClass
public class ReflectUtils {

    /**
     * 字段缓存
     */
    private static final MultiValuedMap<Class<?>, Field> FIELDS_CACHE = new HashSetValuedHashMap<>();

    /**
     * 实例化对象
     *
     * @param <T>   对象类型
     * @param clazz 类名
     * @return 对象
     */
    @SneakyThrows
    public static <T> T newInstance(Class<T> clazz) {
        return clazz.getDeclaredConstructor().newInstance();
    }

    /**
     * 查找指定类中是否包含指定名称对应的字段，包括所有字段（包括非public字段），也包括父类和Object类的字段
     *
     * @param clazz     被查找字段的类
     * @param fieldName 字段名
     * @return 是否包含字段
     */
    public static boolean hasField(Class<?> clazz, String fieldName) throws SecurityException {
        return null != getField(clazz, fieldName);
    }

    /**
     * 获取指定对象的指定属性
     *
     * @param obj       指定对象
     * @param fieldName 指定属性名称
     * @param clazz     指定转换类型
     * @return 指定属性值
     */
    @SuppressWarnings("unchecked")
    public static <T> T getFieldValue(Object obj, String fieldName, Class<T> clazz) {
        if (clazz == null) {
            return null;
        }

        return (T) getFieldValue(obj, fieldName);
    }

    /**
     * 获取指定对象的指定属性
     *
     * @param obj       指定对象
     * @param fieldName 指定属性名称
     * @return 指定属性值
     */
    @SneakyThrows
    public static Object getFieldValue(Object obj, String fieldName) {
        if (obj == null || fieldName == null) {
            return null;
        }
        if (obj instanceof Class) {
            // 静态字段获取时对象为null
            return null;
        }

        Object result = null;
        Field field = getField(obj.getClass(), fieldName);
        if (field != null) {
            field.setAccessible(true);
            result = field.get(obj);
        }
        return result;
    }

    /**
     * 获取指定对象里面的指定属性对象
     *
     * @param clazz     目标类型
     * @param fieldName 指定属性名称
     * @return 属性对象
     */
    public static Field getField(Class<?> clazz, String fieldName) {
        if (fieldName == null) {
            return null;
        }

        Field field = null;
        for (Field f : getFields(clazz)) {
            if (fieldName.equals(f.getName())) {
                field = f;
                break;
            }
        }

        return field;
    }

    /**
     * 获取字段列表
     *
     * <p>父类属性不会被读取
     *
     * @param clazz 类
     * @return 字段列表
     */
    public static Field[] getFields(Class<?> clazz) {
        if (clazz == null) {
            return null;
        }

        Collection<Field> fieldList = FIELDS_CACHE.get(clazz);
        if (fieldList.isEmpty()) {
            Field[] fields = clazz.getDeclaredFields();
            fieldList = Arrays.asList(fields);
            FIELDS_CACHE.putAll(clazz, fieldList);
        }

        return fieldList.toArray(new Field[0]);
    }

    /**
     * 设置指定对象的指定属性值
     *
     * @param obj        指定对象
     * @param fieldName  指定属性
     * @param fieldValue 指定属性值
     */
    @SneakyThrows
    public static void setFieldValue(Object obj, String fieldName, Object fieldValue) {
        Field field = getField(obj.getClass(), fieldName);
        if (field != null) {
            field.setAccessible(true);
            field.set(obj, fieldValue);
            field.setAccessible(false);
        }
    }
}
